/****************************************************************************/
/*  File:       Repository.java                                             */
/*  Author:     F. Georges                                                  */
/*  Company:    H2O Consulting                                              */
/*  Date:       2009-10-19                                                  */
/*  Tags:                                                                   */
/*      Copyright (c) 2009 Florent Georges (see end of file.)               */
/* ------------------------------------------------------------------------ */


package org.expath.pkg.repo;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URLConnection;
import java.util.Collection;
import javax.xml.transform.URIResolver;
import org.apache.xml.resolver.CatalogManager;
import org.apache.xml.resolver.tools.CatalogResolver;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.EntityResolver;

/**
 * ...
 *
 * TODO: Adapt to StAX (JSR 173) instead of Saxon's XdmNode API.
 *
 * @author Florent Georges
 * @date   2009-10-19
 */
public class Repository
{
    /**
     * ...
     *
     * TODO: Check if the root directory does actually exist.  Creating a new
     * repository would be done by a static method.
     */
    public Repository(File root)
    {
        myRoot = root;
    }

    /**
     * ...
     */
    public File getRoot()
    {
        return myRoot;
    }

    public static Repository createRepository(File dir)
            throws PackageException
    {
        if ( dir.exists() ) {
            // must be a dir and empty, or that's an error
            if ( ! dir.isDirectory() || dir.list() == null ) {
                throw new PackageException("File exists and is not a directory (" + dir + ")");
            }
            if ( dir.list().length > 0 ) {
                throw new PackageException("Directory exists and is not empty (" + dir + ")");
            }
            // TODO: Add a force option to delete the dir if it exists and is
            // not empty?
        }
        else {
            if ( ! dir.mkdir() ) {
                throw new PackageException("Error creating the directory (" + dir + ")");
            }
        }
        // here, we know 'dir' is a directory and is empty...
        File priv_dir = new File(dir, ".expath-pkg/");
        if ( ! priv_dir.mkdir() ) {
            throw new PackageException("Error creating the private directory (" + priv_dir + ")");
        }
        // TODO: FIXME: Saxon specific stuff...!
        File saxon_dir = new File(priv_dir, ".saxon/");
        if ( ! saxon_dir.mkdir() ) {
            throw new PackageException("Error creating the Saxon private directory (" + saxon_dir + ")");
        }
        File empty_wrapper = new File(saxon_dir, "empty-wrapper.xsl");
        try {
            ClassLoader loader = Repository.class.getClassLoader();
            InputStream  in  = loader.getResourceAsStream("org/expath/pkg/repo/saxon-empty-wrapper.xsl");
            OutputStream out = new FileOutputStream(empty_wrapper);
            byte[] buf = new byte[4096];
            int len;
            while ( (len = in.read(buf)) > 0 ) {
                out.write(buf, 0, len);
            }
            in.close();
            out.close();
        }
        catch ( IOException ex ) {
            throw new PackageException("Error copying the resource to " + empty_wrapper, ex);
        }
        return new Repository(dir);
    }

    public String[] listPackages()
    {
        return myRoot.list(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return ! ".expath-pkg".equals(name);
            }
        });
    }

    /**
     * Install a XAR package into this repository.
     *
     * If force is false, this is an error if the same package has already
     * been installed in the repository.  If it is true, it is first deleted
     * if existing.
     */
    public void installPackage(File pkg, boolean force, UserInteractionStrategy interact)
            throws PackageException
    {
        PkgInstaller installer = new PkgInstaller(this, interact);
        installer.installPackage(pkg, force);
    }

    public void installPackage(URI pkg, boolean force, UserInteractionStrategy interact)
            throws PackageException
    {
        File downloaded;
        try {
            URLConnection connection = pkg.toURL().openConnection();
            InputStream instream = connection.getInputStream();
            BufferedInputStream in = new BufferedInputStream(instream);
            // just to get the name, to have a meaningful name for the tmp file
            File path = new File(pkg.getPath());
            downloaded = File.createTempFile(path.getName() + "-", "-expath-tmp.xar");
            FileOutputStream tmp = new FileOutputStream(downloaded);
            BufferedOutputStream out = new BufferedOutputStream(tmp);
            int i;
            byte[] buf = new byte[4096];
            while ( (i = in.read(buf)) != -1 ) {
                out.write(buf, 0, i);
            }
            out.close();
            in.close();
        }
        catch ( MalformedURLException ex ) {
            // FIXME:
            throw new RuntimeException(ex);
        }
        catch ( IOException ex ) {
            // FIXME:
            throw new RuntimeException(ex);
        }
        installPackage(downloaded, force, interact);
    }

    /**
     * Provide a URI resolver for a specific URI space.
     *
     * TODO: Add the ability to set a parent resolver, so in some context, we
     * would be able to wrap an existing resolver.
     *
     * TODO: Cache the resolver per space...
     *
     * @see URISpace
     */
    public URIResolver getURIResolver(URISpace space)
            throws PackageException
    {
        return new CatalogResolver(getCatalogManager(space));
    }

    /**
     * Create a new URI resolver from a catalog file.
     *
     * The parameter is a string with a path relative to the repository root
     * directory.
     */
    public URIResolver getURIResolver(String relative_catalog)
            throws PackageException
    {
        URI absolute = new File(myRoot, relative_catalog).toURI();
        return new CatalogResolver(getCatalogManager(absolute));
    }

    /**
     * Create a new URI resolver from a catalog file.
     *
     * The parameter is a string with a path relative to the repository root
     * directory.
     */
    public URIResolver getURIResolver(Collection<String> relative_catalogs)
            throws PackageException
    {
        CatalogManager mgr = getCatalogManager(relative_catalogs);
        return new CatalogResolver(mgr);
    }

    /**
     * ...
     */
    public EntityResolver getEntityResolver(URISpace space)
            throws PackageException
    {
        return new CatalogResolver(getCatalogManager(space));
    }

    /**
     * ...
     */
    public EntityResolver getEntityResolver(String relative_catalog)
            throws PackageException
    {
        URI absolute = new File(myRoot, relative_catalog).toURI();
        return new CatalogResolver(getCatalogManager(absolute));
    }

    /**
     * ...
     */
    public LSResourceResolver getLSResourceResolver(final URISpace space)
            throws PackageException
    {
        return new PkgLSResourceResolver(this, space);
    }

    /**
     * Return the catalog manager for a given space.
     */
    public CatalogManager getCatalogManager(URISpace space)
            throws PackageException
    {
        return getCatalogManager(getSpaceCatalog(space));
    }

    /**
     * Return the catalog manager for a given catalog (passed by URI.)
     */
    private CatalogManager getCatalogManager(URI catalog)
            throws PackageException
    {
        return getCatalogManager(catalog.toString());
    }

    /**
     * Return the catalog manager for a given catalog list (path-like string.)
     */
    public CatalogManager getCatalogManager(Collection<String> relative_catalogs)
            throws PackageException
    {
        StringBuilder buf = new StringBuilder();
        boolean first = true;
        for ( String rel : relative_catalogs ) {
            URI absolute = new File(myRoot, rel).toURI();
            if ( first ) {
                first = false;
            }
            else {
                buf.append(';');
            }
            buf.append(absolute.toString());
        }
        return getCatalogManager(buf.toString());
    }

    /**
     * Return the catalog manager for a given catalog list (path-like string.)
     */
    private CatalogManager getCatalogManager(String catalogs)
            throws PackageException
    {
        CatalogManager mgr = new CatalogManager();
        mgr.setUseStaticCatalog(false);
        mgr.setCatalogFiles(catalogs);
        return mgr;
    }

    /**
     * Return the URI of the catalog file for a given space.
     */
    private URI getSpaceCatalog(URISpace space)
            throws PackageException
    {
        String cat;
        switch ( space ) {
            case RNC:
                cat = ".expath-pkg/rnc-catalog.xml";
                break;
            case RNG:
                cat = ".expath-pkg/rng-catalog.xml";
                break;
            case SCHEMATRON:
                cat = ".expath-pkg/schematron-catalog.xml";
                break;
            case XPROC:
                cat = ".expath-pkg/xproc-catalog.xml";
                break;
            case XQUERY:
                cat = ".expath-pkg/xquery-catalog.xml";
                break;
            case XSD:
                cat = ".expath-pkg/xsd-catalog.xml";
                break;
            case XSLT:
                cat = ".expath-pkg/xslt-catalog.xml";
                break;
            default:
                throw new PackageException("Unknown URI space: " + space);
        }
        return new File(myRoot, cat).toURI();
    }

    /**
     * ...
     */
    private File myRoot;
}


/* ------------------------------------------------------------------------ */
/*  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS COMMENT.               */
/*                                                                          */
/*  The contents of this file are subject to the Mozilla Public License     */
/*  Version 1.0 (the "License"); you may not use this file except in        */
/*  compliance with the License. You may obtain a copy of the License at    */
/*  http://www.mozilla.org/MPL/.                                            */
/*                                                                          */
/*  Software distributed under the License is distributed on an "AS IS"     */
/*  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See    */
/*  the License for the specific language governing rights and limitations  */
/*  under the License.                                                      */
/*                                                                          */
/*  The Original Code is: all this file.                                    */
/*                                                                          */
/*  The Initial Developer of the Original Code is Florent Georges.          */
/*                                                                          */
/*  Contributor(s): none.                                                   */
/* ------------------------------------------------------------------------ */
